內容涵蓋 OpenCV,它是一個圖像和視頻處理庫,包含 C ++,C,Python 和 Java 的綁定。 OpenCV 用於各種圖像和視頻分析,如面部識別和檢測,車牌閱讀,照片編輯,高級機器人視覺,光學字符識別等等。
你將需要兩個主要的庫,第三個可選:python-OpenCV,Numpy 和 Matplotlib。
Windows 用戶:
python-OpenCV:有其他的方法,但這是最簡單的。 下載相應的 wheel(.whl)文件,然後使用pip進行安裝。 觀看視頻來尋求幫助。
pip install numpy
pip install matplotlib
不熟悉使用pip? 請參閱pip安裝教程來獲得幫助。
Linux/Mac 用戶
pip3 install numpy
或者
apt-get install python3-numpy
你可能需要apt-get來安裝python3-pip。
pip3 install matplotlib
或者
apt-get install python3-matplotlib
apt-get install python-OpenCV
Matplotlib 是用於展示來自視頻或圖像的幀的可選選項。 我們將在這裡展示幾個使用它的例子。 Numpy 被用於「數值和 Python」的所有東西。 我們主要利用 Numpy 的數組功能。 最後,我們使用python-OpenCV,它是 Python 特定的 OpenCV 綁定。
OpenCV 有一些操作,如果沒有完整安裝 OpenCV (大小約 3GB),你將無法完成,但是實際上你可以用 python-OpenCV 最簡安裝。 我們將在本系列的後續部分中使用 OpenCV 的完整安裝,如果你願意的話,你可以隨意獲得它,但這三個模塊將使我們忙碌一段時間!
通過運行 Python 並執行下列命令來確保你安裝成功:
import cv2
import matplotlib
import numpy
如果你沒有錯誤,那麼你已經準備好了。好了嘛?讓我們下潛吧!
首先,在圖像和視頻分析方面,我們應該了解一些基本的假設和範式。對現在每個攝像機的記錄方式來說,記錄實際上是一幀一幀地顯示,每秒 30-60 次。但是,它們的核心是靜態幀,就像圖像一樣。因此,圖像識別和視頻分析大部分使用相同的方法。有些東西,如方向跟蹤,將需要連續的圖像(幀),但像面部檢測或物體識別等東西,在圖像和視頻中代碼幾乎完全相同。
接下來,大量的圖像和視頻分析歸結為儘可能簡化來源。這幾乎總是起始於轉換為灰度,但也可以是彩色濾鏡,漸變或這些的組合。從這裡,我們可以對來源執行各種分析和轉化。一般來說,這裡發生的事情是轉換完成,然後是分析,然後是任何覆蓋,我們希望應用在原始來源上,這就是你可以經常看到,對象或面部識別的「成品」在全色圖像或視頻上顯示。然而,數據實際上很少以這種原始形式處理。有一些我們可以在基本層面上做些什麼的例子。所有這些都使用基本的網絡攝像頭來完成,沒有什麼特別的:
背景提取
顏色過濾
邊緣檢測
用於對象識別的特徵匹配
一般對象識別
在邊緣檢測的情況下,黑色對應於(0,0,0)的像素值,而白色線條是(255,255,255)。視頻中的每個圖片和幀都會像這樣分解為像素,並且像邊緣檢測一樣,我們可以推斷,邊緣是基於白色與黑色像素對比的地方。然後,如果我們想看到標記邊緣的原始圖像,我們記錄下白色像素的所有坐標位置,然後在原始圖像或視頻上標記這些位置。
到本教程結束時,你將能夠完成上述所有操作,並且能夠訓練你的機器識別你想要的任何對象。就像我剛開始說的,第一步通常是轉換為灰度。在此之前,我們需要加載圖像。因此,我們來做吧!在整個教程中,我極力鼓勵你使用你自己的數據來玩。如果你有攝像頭,一定要使用它,否則找到你認為很有趣的圖像。如果你有麻煩,這是一個手錶的圖像:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
首先,我們正在導入一些東西,我已經安裝了這三個模塊。接下來,我們將img定義為cv2.read(image file, parms)。默認值是IMREAD_COLOR,這是沒有任何 alpha 通道的顏色。如果你不熟悉,alpha 是不透明度(與透明度相反)。如果你需要保留 Alpha 通道,也可以使用IMREAD_UNCHANGED。很多時候,你會讀取顏色版本,然後將其轉換為灰度。如果你沒有網絡攝像機,這將是你在本教程中使用的主要方法,即加載圖像。
你可以不使用IMREAD_COLOR ...等,而是使用簡單的數字。你應該熟悉這兩種選擇,以便了解某個人在做什麼。對於第二個參數,可以使用-1,0或1。顏色為1,灰度為0,不變為-1。因此,對於灰度,可以執行cv2.imread('watch.jpg', 0)。
一旦加載完成,我們使用cv2.imshow(title,image)來顯示圖像。從這裡,我們使用cv2.waitKey(0)來等待,直到有任何按鍵被按下。一旦完成,我們使用cv2.destroyAllWindows()來關閉所有的東西。
正如前面提到的,你也可以用 Matplotlib 顯示圖像,下面是一些如何實現的代碼:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)
plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([]) # to hide tick values on X and Y axis
plt.plot([200,300,400],[100,200,300],'c', linewidth=5)
plt.show()
請注意,你可以繪製線條,就像任何其他 Matplotlib 圖表一樣,使用像素位置作為坐標的。 不過,如果你想繪製你的圖片,Matplotlib 不是必需的。 OpenCV 為此提供了很好的方法。 當你完成修改後,你可以保存,如下所示:
cv2.imwrite('watchgray.png',img)
將圖片導入 OpenCV 似乎很容易,加載視頻源如何? 在下一個教程中,我們將展示如何加載攝像頭或視頻源。
二、加載視頻源
在這個 Python OpenCV 教程中,我們將介紹一些使用視頻和攝像頭的基本操作。 除了起始行,處理來自視頻的幀與處理圖像是一樣的。 我們來舉例說明一下:
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
while(True):
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('frame',gray)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
首先,我們導入numpy和cv2,沒有什麼特別的。 接下來,我們可以cap = cv2.VideoCapture(0)。 這將從你計算機上的第一個網絡攝像頭返回視頻。 如果你正在觀看視頻教程,你將看到我正在使用1,因為我的第一個攝像頭正在錄製我,第二個攝像頭用於實際的教程源。
while(True):
ret, frame = cap.read()
這段代碼啟動了一個無限循環(稍後將被break語句打破),其中ret和frame被定義為cap.read()。 基本上,ret是一個代表是否有返回的布爾值,frame是每個返回的幀。 如果沒有幀,你不會得到錯誤,你會得到None。
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
在這裡,我們定義一個新的變量gray,作為轉換為灰度的幀。 注意這個BGR2GRAY。 需要注意的是,OpenCV 將顏色讀取為 BGR(藍綠色紅色),但大多數計算機應用程式讀取為 RGB(紅綠藍)。 記住這一點。
cv2.imshow('frame',gray)
請注意,儘管是視頻流,我們仍然使用imshow。 在這裡,我們展示了轉換為灰色的源。 如果你想同時顯示,你可以對原始幀和灰度執行imshow,將出現兩個窗口。
if cv2.waitKey(1) & 0xFF == ord('q'):
break
這個語句每幀只運行一次。 基本上,如果我們得到一個按鍵,那個鍵是q,我們將退出while循環,然後運行:
cap.release()
cv2.destroyAllWindows()
這將釋放網絡攝像頭,然後關閉所有的imshow()窗口。
在某些情況下,你可能實際上需要錄製,並將錄製內容保存到新文件中。 以下是在 Windows 上執行此操作的示例:
import numpy as np
import cv2
cap = cv2.VideoCapture(1)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))
while(True):
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
out.write(frame)
cv2.imshow('frame',gray)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
out.release()
cv2.destroyAllWindows()
這裡主要要注意的是正在使用的編解碼器,以及在while循環之前定義的輸出信息。 然後,在while循環中,我們使用out.write()來輸出幀。 最後,在while循環之外,在我們釋放攝像頭之後,我們也釋放out。
太好了,現在我們知道如何操作圖像和視頻。 如果你沒有網絡攝像頭,你可以使用圖像甚至視頻來跟隨教程的其餘部分。 如果你希望使用視頻而不是網絡攝像頭作為源,則可以為視頻指定文件路徑,而不是攝像頭號碼。
現在我們可以使用來源了,讓我們來展示如何繪製東西。 此前你已經看到,你可以使用 Matplotlib 在圖片頂部繪製,但是 Matplotlib 並不真正用於此目的,特別是不能用於視頻源。 幸運的是,OpenCV 提供了一些很棒的工具,來幫助我們實時繪製和標記我們的源,這就是我們將在下一個教程中討論的內容。
三、在圖像上繪製和寫字
在這個 Python OpenCV 教程中,我們將介紹如何在圖像和視頻上繪製各種形狀。 想要以某種方式標記檢測到的對象是相當普遍的,所以我們人類可以很容易地看到我們的程序是否按照我們的希望工作。 一個例子就是之前顯示的圖像之一:
對於這個臨時的例子,我將使用下面的圖片:
鼓勵你使用自己的圖片。 像往常一樣,我們的起始代碼可以是這樣的:
import numpy as np
import cv2
img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)
下面,我們可以開始繪製,這樣:
cv2.line(img,(0,0),(150,150),(255,255,255),15)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.line()接受以下參數:圖片,開始坐標,結束坐標,顏色(bgr),線條粗細。
結果在這裡:
好吧,很酷,讓我們繪製更多形狀。 接下來是一個矩形:
cv2.rectangle(img,(15,25),(200,150),(0,0,255),15)
這裡的參數是圖像,左上角坐標,右下角坐標,顏色和線條粗細。
圓怎麼樣?
cv2.circle(img,(100,63), 55, (0,255,0), -1)
這裡的參數是圖像/幀,圓心,半徑,顏色和。 注意我們粗細為-1。 這意味著將填充對象,所以我們會得到一個圓。
線條,矩形和圓都很酷,但是如果我們想要五邊形,八邊形或十八邊形? 沒問題!
pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
#pts = pts.reshape((-1,1,2))
cv2.polylines(img, [pts], True, (0,255,255), 3)
首先,我們將坐標數組稱為pts(點的簡稱)。 然後,我們使用cv2.polylines來畫線。 參數如下:繪製的對象,坐標,我們應該連接終止的和起始點,顏色和粗細。
你可能想要做的最後一件事是在圖像上寫字。 這可以這樣做:
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV Tuts!',(0,130), font, 1, (200,255,155), 2, cv2.LINE_AA)
目前為止的完整代碼:
import numpy as np
import cv2
img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)
cv2.line(img,(0,0),(200,300),(255,255,255),50)
cv2.rectangle(img,(500,250),(1000,500),(0,0,255),15)
cv2.circle(img,(447,63), 63, (0,255,0), -1)
pts = np.array([[100,50],[200,300],[700,200],[500,100]], np.int32)
pts = pts.reshape((-1,1,2))
cv2.polylines(img, [pts], True, (0,255,255), 3)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV Tuts!',(10,500), font, 6, (200,255,155), 13, cv2.LINE_AA)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
結果:
在下一個教程中,我們將介紹我們可以執行的基本圖像操作。
四、圖像操作
在 OpenCV 教程中,我們將介紹一些我們可以做的簡單圖像操作。 每個視頻分解成幀。 然後每一幀,就像一個圖像,分解成存儲在行和列中的,幀/圖片中的像素。 每個像素都有一個坐標位置,每個像素都由顏色值組成。 讓我們列舉訪問不同的位的一些例子。
我們將像往常一樣讀取圖像(如果可以,請使用自己的圖像,但這裡是我在這裡使用的圖像):
import cv2
import numpy as np
img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)
現在我們可以實際引用特定像素,像這樣:
px = img[55,55]
下面我們可以實際修改像素:
img[55,55] = [255,255,255]
之後重新引用:
px = img[55,55]
print(px)
現在應該不同了,下面我們可以引用 ROI,圖像區域:
px = img[100:150,100:150]
print(px)
我們也可以修改 ROI,像這樣:
img[100:150,100:150] = [255,255,255]
我們可以引用我們的圖像的特定特徵:
print(img.shape)
print(img.size)
print(img.dtype)
我們可以像這樣執行操作:
watch_face = img[37:111,107:194]
img[0:74,0:87] = watch_face
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
這會處理我的圖像,但是可能不能用於你的圖像,取決於尺寸。這是我的輸出:
這些是一些簡單的操作。 在下一個教程中,我們將介紹一些我們可以執行的更高級的圖像操作。
五、圖像算術和邏輯運算
歡迎來到另一個 Python OpenCV 教程,在本教程中,我們將介紹一些簡單算術運算,我們可以在圖像上執行的,並解釋它們的作用。 為此,我們將需要兩個相同大小的圖像來開始,然後是一個較小的圖像和一個較大的圖像。 首先,我將使用:
和
首先,讓我們看看簡單的加法會做什麼:
import cv2
import numpy as np
img1 = cv2.imread('3D-Matplotlib.png')
img2 = cv2.imread('mainsvmimage.png')
add = img1+img2
cv2.imshow('add',add)
cv2.waitKey(0)
cv2.destroyAllWindows()
結果:
你不可能想要這種混亂的加法。 OpenCV 有一個「加法」方法,讓我們替換以前的「加法」,看看是什麼:
add = cv2.add(img1,img2)
結果:
這裡可能不理想。 我們可以看到很多圖像是非常「白色的」。 這是因為顏色是 0-255,其中 255 是「全亮」。 因此,例如:(155,211,79) + (50, 170, 200) = 205, 381, 279...轉換為(205, 255,255)。
接下來,我們可以添加圖像,並可以假設每個圖像都有不同的「權重」。 這是如何工作的:
import cv2
import numpy as np
img1 = cv2.imread('3D-Matplotlib.png')
img2 = cv2.imread('mainsvmimage.png')
weighted = cv2.addWeighted(img1, 0.6, img2, 0.4, 0)
cv2.imshow('weighted',weighted)
cv2.waitKey(0)
cv2.destroyAllWindows()
對於addWeighted方法,參數是第一個圖像,權重,第二個圖像,權重,然後是伽馬值,這是一個光的測量值。 我們現在就把它保留為零。
這些是一些額外的選擇,但如果你真的想將一個圖像添加到另一個,最新的重疊在哪裡? 在這種情況下,你會從最大的開始,然後添加較小的圖像。 為此,我們將使用相同的3D-Matplotlib.png圖像,但使用一個新的 Python 標誌:
現在,我們可以選取這個標誌,並把它放在原始圖像上。 這很容易(基本上使用我們在前一個教程中使用的相同代碼,我們用一個新的東西替換了圖像區域(ROI)),但是如果我們只想要標誌部分而不是白色背景呢? 我們可以使用與之前用於 ROI 替換相同的原理,但是我們需要一種方法來「去除」標誌的背景,使得白色不會不必要地阻擋更多背景圖像。 首先我將顯示完整的代碼,然後解釋:
import cv2
import numpy as np
img1 = cv2.imread('3D-Matplotlib.png')
img2 = cv2.imread('mainlogo.png')
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 220, 255, cv2.THRESH_BINARY_INV)
mask_inv = cv2.bitwise_not(mask)
img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
dst = cv2.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv2.imshow('res',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
這裡發生了很多事情,出現了一些新的東西。 我們首先看到的是一個新的閾值:ret, mask = cv2.threshold(img2gray, 220, 255, cv2.THRESH_BINARY_INV)。
我們將在下一個教程中介紹更多的閾值,所以請繼續關注具體內容,但基本上它的工作方式是根據閾值將所有像素轉換為黑色或白色。 在我們的例子中,閾值是 220,但是我們可以使用其他值,或者甚至動態地選擇一個,這是ret變量可以使用的值。 接下來,我們看到:mask_inv = cv2.bitwise_not(mask)。 這是一個按位操作。 基本上,這些操作符與 Python 中的典型操作符非常相似,除了一點,但我們不會在這裡觸及它。 在這種情況下,不可見的部分是黑色的地方。 然後,我們可以說,我們想在第一個圖像中將這個區域遮住,然後將空白區域替換為圖像 2 的內容。
下個教程中,我們深入討論閾值。
六、閾值
歡迎閱讀另一個 OpenCV 教程。在本教程中,我們將介紹圖像和視頻分析的閾值。閾值的思想是進一步簡化視覺數據的分析。首先,你可以轉換為灰度,但是你必須考慮灰度仍然有至少 255 個值。閾值可以做的事情,在最基本的層面上,是基於閾值將所有東西都轉換成白色或黑色。比方說,我們希望閾值為 125(最大為 255),那麼 125 以下的所有內容都將被轉換為 0 或黑色,而高於 125 的所有內容都將被轉換為 255 或白色。如果你像平常一樣轉換成灰度,你會變成白色和黑色。如果你不轉換灰度,你會得到二值化的圖片,但會有顏色。
雖然這聽起來不錯,但通常不是。我們將在這裡介紹多個示例和不同類型的閾值來說明這一點。我們將使用下面的圖片作為我們的示例圖片,但可以隨意使用你自己的圖片:
這個書的圖片就是個很好的例子,說明為什麼一個人可能需要閾值。 首先,背景根本沒有白色,一切都是暗淡的,而且一切都是變化的。 有些部分很容易閱讀,另一部分則非常暗,需要相當多的注意力才能識別出來。 首先,我們嘗試一個簡單的閾值:
retval, threshold = cv2.threshold(img, 10, 255, cv2.THRESH_BINARY)
二元閾值是個簡單的「是或不是」的閾值,其中像素為 255 或 0。在很多情況下,這是白色或黑色,但我們已經為我們的圖像保留了顏色,所以它仍然是彩色的。 這裡的第一個參數是圖像。 下一個參數是閾值,我們選擇 10。下一個是最大值,我們選擇為 255。最後是閾值類型,我們選擇了THRESH_BINARY。 通常情況下,10 的閾值會有點差。 我們選擇 10,因為這是低光照的圖片,所以我們選擇低的數字。 通常 125-150 左右的東西可能效果最好。
import cv2
import numpy as np
img = cv2.imread('bookpage.jpg')
retval, threshold = cv2.threshold(img, 12, 255, cv2.THRESH_BINARY)
cv2.imshow('original',img)
cv2.imshow('threshold',threshold)
cv2.waitKey(0)
cv2.destroyAllWindows()
結果:
現在的圖片稍微更便於閱讀了,但還是有點亂。 從視覺上來說,這樣比較好,但是仍然難以使用程序來分析它。 讓我們看看我們是否可以進一步簡化。
首先,讓我們灰度化圖像,然後使用一個閾值:
import cv2
import numpy as np
grayscaled = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
retval, threshold = cv2.threshold(grayscaled, 10, 255, cv2.THRESH_BINARY)
cv2.imshow('original',img)
cv2.imshow('threshold',threshold)
cv2.waitKey(0)
cv2.destroyAllWindows()
更簡單,但是我們仍然在這裡忽略了很多背景。 接下來,我們可以嘗試自適應閾值,這將嘗試改變閾值,並希望弄清楚彎曲的頁面。
import cv2
import numpy as np
th = cv2.adaptiveThreshold(grayscaled, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 115, 1)
cv2.imshow('original',img)
cv2.imshow('Adaptive threshold',th)
cv2.waitKey(0)
cv2.destroyAllWindows()
還有另一個版本的閾值,可以使用,叫做大津閾值。 它在這裡並不能很好發揮作用,但是:
retval2,threshold2 = cv2.threshold(grayscaled,125,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('original',img)
cv2.imshow('Otsu threshold',threshold2)
cv2.waitKey(0)
cv2.destroyAllWindows()
七、顏色過濾
在這個 Python OpenCV 教程中,我們將介紹如何創建一個過濾器,回顧按位操作,其中我們將過濾特定的顏色,試圖顯示它。或者,你也可以專門篩選出特定的顏色,然後將其替換為場景,就像我們用其他方法替換ROI(圖像區域)一樣,就像綠屏的工作方式。
為了像這樣過濾,你有幾個選項。通常,你可能會將你的顏色轉換為 HSV,即「色調飽和度純度」。例如,這可以幫助你根據色調和飽和度範圍,使用變化的值確定一個更具體的顏色。如果你希望的話,你可以實際生成基於 BGR 值的過濾器,但是這會有點困難。如果你很難可視化 HSV,不要感到失落,查看維基百科頁面上的 HSV,那裡有一個非常有用的圖形讓你可視化它。我最好親自描述顏色的色調飽和度和純度。現在讓我們開始:
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
while(1):
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([30,150,50])
upper_red = np.array([255,255,180])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(frame,frame, mask= mask)
cv2.imshow('frame',frame)
cv2.imshow('mask',mask)
cv2.imshow('res',res)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cap.release()
這只是一個例子,以紅色為目標。 它的工作方式是,我們所看到的是我們範圍內的任何東西,基本上是 30-255,150-255 和 50-180。 它用於紅色,但可以隨便嘗試找到自己的顏色。 HSV 在這裡效果最好的原因是,我們想要範圍內的顏色,這裡我們通常需要相似的顏色。 很多時候,典型的紅色仍然會有一些綠色和藍色分量,所以我們必須允許一些綠色和藍色,但是我們會想要幾乎全紅。 這意味著我們會在這裡獲得所有顏色的低光混合。
為了確定 HSV 的範圍,我認為最好的方法就是試錯。 OpenCV 內置了將 BGR 轉換為 HSV 的方法。 如果你想挑選單一的顏色,那麼 BGR 到 HSV 將會很好用。 為了教學,下面是這個代碼的一個例子:
dark_red = np.uint8([[[12,22,121]]])
dark_red = cv2.cvtColor(dark_red,cv2.COLOR_BGR2HSV)
這裡的結果是一個 HSV 值,與dark_red值相同。這很棒...但是,同樣...你遇到了顏色範圍和 HSV 範圍的基本問題。他們根本不同。你可能合理使用 BGR 範圍,它們仍然可以工作,但是對於檢測一種「顏色」,則無法正常工作。
回到主代碼,然而,我們首先要把幀轉換成 HSV。那裡沒什麼特別的。接下來,我們為紅色指定一些 HSV 值。我們使用inRange函數,為我們的特定範圍創建掩碼。這是真或假,黑色或白色。接下來,我們通過執行按位操作來「恢復」我們的紅色。基本上,我們顯示了frame and mask。掩碼的白色部分是紅色範圍,被轉換為純白色,而其他一切都變成黑色。最後我們展示所有東西。我選擇了顯示原始真,掩碼和最終結果,以便更好地了解發生的事情。
在下一個教程中,我們將對這個主題做一些介紹。你可能看到了,我們在這裡還是有一些「噪音」。東西有顆粒感,紅色中的黑點很多,還有許多其他的小色點。我們可以做一些事情,試圖通過模糊和平滑來緩解這個問題,接下來我們將討論這個問題。
八、模糊和平滑
在這個 Python OpenCV 教程中,我們將介紹如何嘗試從我們的過濾器中消除噪聲,例如簡單的閾值,或者甚至我們以前的特定的顏色過濾器:
正如你所看到的,我們有很多黑點,其中我們喜歡紅色,還有很多其他的色點散落在其中。 我們可以使用各種模糊和平滑技術來嘗試彌補這一點。 我們可以從一些熟悉的代碼開始:
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
while(1):
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([30,150,50])
upper_red = np.array([255,255,180])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(frame,frame, mask= mask)
現在,讓我們應用一個簡單的平滑,我們計算每個像素塊的均值。 在我們的例子中,我們使用15x15正方形,這意味著我們有 225 個總像素。
kernel = np.ones((15,15),np.float32)/225
smoothed = cv2.filter2D(res,-1,kernel)
cv2.imshow('Original',frame)
cv2.imshow('Averaging',smoothed)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cap.release()
這個很簡單,但是結果犧牲了很多粒度。 接下來,讓我們嘗試一些高斯模糊:
blur = cv2.GaussianBlur(res,(15,15),0)
cv2.imshow('Gaussian Blurring',blur)
另一個選項是中值模糊:
median = cv2.medianBlur(res,15)
cv2.imshow('Median Blur',median)
最後一個選項是雙向模糊:
bilateral = cv2.bilateralFilter(res,15,75,75)
cv2.imshow('bilateral Blur',bilateral)
所有模糊的對比:
至少在這種情況下,我可能會使用中值模糊,但是不同的照明,不同的閾值/過濾器,以及其他不同的目標和目標可能會決定你使用其中一個。
在下一個教程中,我們將討論形態變換。
九、形態變換
在這個 Python OpenCV 教程中,我們將介紹形態變換。 這些是一些簡單操作,我們可以基於圖像形狀執行。
我們要談的第一對是腐蝕和膨脹。 腐蝕是我們將「腐蝕」邊緣。 它的工作方式是使用滑塊(核)。 我們讓滑塊滑動,如果所有的像素是白色的,那麼我們得到白色,否則是黑色。 這可能有助於消除一些白色噪音。 另一個版本是膨脹,它基本上是相反的:讓滑塊滑動,如果整個區域不是黑色的,就會轉換成白色。 這是一個例子:
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
while(1):
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([30,150,50])
upper_red = np.array([255,255,180])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(frame,frame, mask= mask)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(mask,kernel,iterations = 1)
dilation = cv2.dilate(mask,kernel,iterations = 1)
cv2.imshow('Original',frame)
cv2.imshow('Mask',mask)
cv2.imshow('Erosion',erosion)
cv2.imshow('Dilation',dilation)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cap.release()
結果:
下一對是「開放」和「關閉」。 開放的目標是消除「假陽性」。 有時在背景中,你會得到一些像素「噪音」。 「關閉」的想法是消除假陰性。 基本上就是你檢測了你的形狀,例如我們的帽子,但物體仍然有一些黑色像素。 關閉將嘗試清除它們。
cap = cv2.VideoCapture(1)
while(1):
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([30,150,50])
upper_red = np.array([255,255,180])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(frame,frame, mask= mask)
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
cv2.imshow('Original',frame)
cv2.imshow('Mask',mask)
cv2.imshow('Opening',opening)
cv2.imshow('Closing',closing)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cap.release()
另外兩個選項是tophat和blackhat,對我們的案例並不有用:
cv2.imshow('Tophat',tophat)
cv2.imshow('Blackhat',blackhat)
在下一個教程中,我們將討論圖像漸變和邊緣檢測。
十、邊緣檢測和漸變
歡迎閱讀另一個 Python OpenCV 教程。 在本教程中,我們將介紹圖像漸變和邊緣檢測。 圖像漸變可以用來測量方向的強度,邊緣檢測就像它所說的:它找到了邊緣! 我敢打賭你肯定沒看到。
首先,我們來展示一些漸變的例子:
import cv2
import numpy as np
cap = cv2.VideoCapture(1)
while(1):
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([30,150,50])
upper_red = np.array([255,255,180])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(frame,frame, mask= mask)
laplacian = cv2.Laplacian(frame,cv2.CV_64F)
sobelx = cv2.Sobel(frame,cv2.CV_64F,1,0,ksize=5)
sobely = cv2.Sobel(frame,cv2.CV_64F,0,1,ksize=5)
cv2.imshow('Original',frame)
cv2.imshow('Mask',mask)
cv2.imshow('laplacian',laplacian)
cv2.imshow('sobelx',sobelx)
cv2.imshow('sobely',sobely)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cap.release()
如果你想知道什麼是cv2.CV_64F,那就是數據類型。 ksize是核大小。 我們使用 5,所以每次查詢5×5的漁區。
雖然我們可以使用這些漸變轉換為純邊緣,但我們也可以使用 Canny 邊緣檢測!
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
while(1):
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([30,150,50])
upper_red = np.array([255,255,180])
mask = cv2.inRange(hsv, lower_red, upper_red)
res = cv2.bitwise_and(frame,frame, mask= mask)
cv2.imshow('Original',frame)
edges = cv2.Canny(frame,100,200)
cv2.imshow('Edges',edges)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
cap.release()
這真是太棒了! 但是,這並不完美。 注意陰影導致了邊緣被檢測到。 其中最明顯的是藍狗窩發出的陰影。
在下一個 OpenCV 教程中,我們將討論如何在其他圖像中搜索和查找相同的圖像模板。
十一、模板匹配
歡迎閱讀另一個 Python OpenCV 教程,在本教程中,我們將介紹對象識別的一個基本版本。 這裡的想法是,給出一定的閾值,找到匹配我們提供的模板圖像的相同區域。 對於具體的對象匹配,具有精確的照明/刻度/角度,這可以工作得很好。 通常會遇到這些情況的例子就是計算機上的任何 GUI。 按鈕等東西總是相同的,所以你可以使用模板匹配。 結合模板匹配和一些滑鼠控制,你已經實現了一個基於 Web 的機器人!
首先,你需要一個主要圖像和一個模板。 你應該從你正在圖像中查找的「東西」選取你的模板。 我將提供一個圖像作為例子,但隨意使用你最喜愛的網站的圖像或類似的東西。
主要圖像:
我們要搜索的模板:
這只是其中一個埠,但我們很好奇,看看我們是否可以匹配任何其他埠。 我們確實要選擇一個閾值,其中某種東西可能是 80% 匹配,那麼我們說這就匹配。 所以,我們將開始加載和轉換圖像:
import cv2
import numpy as np
img_rgb = cv2.imread('opencv-template-matching-python-tutorial.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('opencv-template-for-matching.jpg',0)
w, h = template.shape[::-1]
到目前為止,我們加載了兩個圖像,轉換為灰度。 我們保留原始的 RGB 圖像,並創建一個灰度版本。 我之前提到過這個,但是我們這樣做的原因是,我們在灰度版本上執行所有的處理,然後在彩色圖像上使用相同的標籤來標記。
對於主要圖像,我們只有彩色版本和灰度版本。 我們加載模板並記下尺寸。
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
在這裡,我們用img_gray(我們的主圖像),模板,和我們要使用的匹配方法調用matchTemplate,並將返回值稱為res。 我們指定一個閾值,這裡是 80%。 然後我們使用邏輯語句,找到res大於或等於 80% 的位置。
最後,我們使用灰度圖像中找到的坐標,標記原始圖像上的所有匹配:
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)
cv2.imshow('Detected',img_rgb)
所以我們得到了幾個匹配。也許需要降低閾值?我們試試 0.7。
這裡有一些假陽性。 你可以繼續調整門檻,直到你達到 100%,但是如果沒有假陽性,你可能永遠不會達到它。 另一個選擇就是使用另一個模板圖像。 有時候,使用相同對象的多個圖像是有用的。 這樣,你可以使閾值足夠高的,來確保你的結果準確。
在下一個教程中,我們將介紹前景提取。
十二、GrabCut 前景提取
歡迎閱讀 Python OpenCV 前景提取教程。 這裡的想法是找到前景,並刪除背景。 這很像綠屏,只是這裡我們實際上不需要綠屏。
首先,我們將使用一個圖像:
隨意使用你自己的。
讓我們加載圖像並定義一些東西:
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('opencv-python-foreground-extraction-tutorial.jpg')
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (161,79,150,150)
到目前為止,我們已經導入了cv2,numpy和matplotlib。 然後我們加載圖像,創建一個掩碼,指定算法內部使用的背景和前景模型。 真正重要的部分是我們定義的矩形。 這是rect = (start_x, start_y, width, height)。
這是包圍我們的主要對象的矩形。 如果你正在使用我的圖片,那就是要使用的矩陣。 如果你使用自己的,找到適合你的圖像的坐標。
下面:
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img)
plt.colorbar()
plt.show()
所以在這裡我們使用了cv2.grabCut,它用了很多參數。 首先是輸入圖像,然後是掩碼,然後是主要對象的矩形,背景模型,前景模型,要運行的疊代量以及使用的模式。
這裡改變了掩碼,使得所有像素 0 和 2 轉換為背景,而像素 1 和 3 現在是前景。 從這裡,我們乘以輸入圖像,得到我們的最終結果:
下個教程中,我們打算討論如何執行角點檢測。
十三、角點檢測
歡迎閱讀 Python OpenCV 角點檢測教程。 檢測角點的目的是追蹤運動,做 3D 建模,識別物體,形狀和角色等。
對於本教程,我們將使用以下圖像:
我們的目標是找到這個圖像中的所有角點。 我會注意到,在這裡我們有一些別名問題(斜線的鋸齒),所以,如果我們允許的話,會發現很多角點,而且是正確的。 和往常一樣,OpenCV 已經為我們完成了難題,我們需要做的就是輸入一些參數。 讓我們開始加載圖像並設置一些參數:
import numpy as np
import cv2
img = cv2.imread('opencv-corner-detection-sample.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 10)
corners = np.int0(corners)
到目前為止,我們加載圖像,轉換為灰度,然後是float32。 接下來,我們用goodFeaturesToTrack函數檢測角點。 這裡的參數是圖像,檢測到的最大角點數量,品質和角點之間的最小距離。 如前所述,我們在這裡的鋸齒問題將允許找到許多角點,所以我們對其進行了限制。 下面:
for corner in corners:
x,y = corner.ravel()
cv2.circle(img,(x,y),3,255,-1)
cv2.imshow('Corner',img)
現在我們遍歷每個角點,在我們認為是角點的每個點上畫一個圓。
在下一個教程中,我們將討論功能匹配/單映射。
十四、特徵匹配(單映射)爆破
歡迎閱讀 Python OpenCV 特徵匹配教程。 特徵匹配將是稍微更令人印象深刻的模板匹配版本,其中需要一個完美的,或非常接近完美的匹配。
我們從我們希望找到的圖像開始,然後我們可以在另一幅圖像中搜索這個圖像。 這裡的完美是圖像不需要相同的光照,角度,旋轉等。 特徵只需要匹配。
首先,我們需要一些示例圖像。 我們的「模板」,或者我們將要嘗試匹配的圖像:
之後是我們用於搜索這個模板的圖像:
在這裡,我們的模板圖像在模板中,比在我們要搜索的圖像中要小一些。 它的旋轉也不同,陰影也有些不同。
現在我們將使用一種「爆破」匹配的形式。 我們將在這兩個圖像中找到所有特徵。 然後我們匹配這些特徵。 然後,我們可以繪製我們想要的,儘可能多的匹配。 但是要小心。 如果你繪製 500 個匹配,你會有很多誤報。 所以只繪製繪製前幾個。
import numpy as np
import cv2
import matplotlib.pyplot as plt
img1 = cv2.imread('opencv-feature-matching-template.jpg',0)
img2 = cv2.imread('opencv-feature-matching-image.jpg',0)
到目前為止,我們已經導入了要使用的模塊,並定義了我們的兩個圖像,即模板(img1)和用於搜索模板的圖像(img2)。
orb = cv2.ORB_create()
這是我們打算用於特徵的檢測器。
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
在這裡,我們使用orb探測器找到關鍵點和他們的描述符。
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
這就是我們的BFMatcher對象。
matches = bf.match(des1,des2)
matches = sorted(matches, key = lambda x:x.distance)
這裡我們創建描述符的匹配,然後根據它們的距離對它們排序。
img3 = cv2.drawMatches(img1,kp1,img2,kp2,matches[:10],None, flags=2)
plt.imshow(img3)
plt.show()
這裡我們繪製了前 10 個匹配。輸出:
十五、MOG 背景減弱
在這個 Python OpenCV 教程中,我們將要討論如何通過檢測運動來減弱圖像的背景。 這將要求我們回顧視頻的使用,或者有兩個圖像,一個沒有你想要追蹤的人物/物體,另一個擁有人物/物體。 如果你希望,你可以使用你的攝像頭,或者使用如下的視頻:
人們行走的樣例視頻
這裡的代碼實際上很簡單,就是我們現在知道的:
import numpy as np
import cv2
cap = cv2.VideoCapture('people-walking.mp4')
fgbg = cv2.createBackgroundSubtractorMOG2()
while(1):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)
cv2.imshow('fgmask',frame)
cv2.imshow('frame',fgmask)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
結果:
pythonprogramming.net/static/imag…
這裡的想法是從靜態背景中提取移動的前景。 你也可以使用這個來比較兩個相似的圖像,並立即提取它們之間的差異。
在我們的例子中,我們可以看到我們確實已經檢測到了一些人,但是我們確實有一些「噪音」,噪音實際上是樹葉在周圍的風中移動了一下。 只要我們知道一種減少噪音的方法。 等一下! 我們的確知道! 一個瘋狂的挑戰已經出現了你面前!
接下來的教程開始讓我們遠離濾鏡或變換的應用,並讓我們使用 Haar Cascades 來檢測一般對象,例如面部檢測等等。
十六、Haar Cascade 面部檢測
在這個 Python OpenCV 教程中,我們將討論 Haar Cascades 對象檢測。我們將從臉部和眼睛檢測來開始。為了使用層疊文件進行對象識別/檢測,首先需要層疊文件。對於非常流行的任務,這些已經存在。檢測臉部,汽車,笑臉,眼睛和車牌等東西都是非常普遍的。
首先,我會告訴你如何使用這些層疊文件,然後我將告訴你如何開始創建你自己的層疊,這樣你就可以檢測到任何你想要的對象,這很酷!
你可以使用 Google 來查找你可能想要檢測的東西的各種 Haar Cascades。對於找到上述類型,你應該沒有太多的麻煩。我們將使用面部層疊和眼睛層疊。你可以在 Haar Cascades 的根目錄找到更多。請注意用於使用/分發這些 Haar Cascades 的許可證。
讓我們開始我們的代碼。我假設你已經從上面的連結中下載了haarcascade_eye.xml和haarcascade_frontalface_default.xml,並將這些文件放在你項目的目錄中。
import numpy as np
import cv2
#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xml
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
cap = cv2.VideoCapture(0)
在這裡,我們從導入cv2和numpy開始,然後加載我們的臉部和眼部的層疊。 目前為止很簡單。
while 1:
ret, img = cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
現在我們開始我們的典型循環,這裡唯一的新事物就是臉部的創建。 更多信息請訪問detectMultiScale函數的文檔。 基本上,它找到了面部! 我們也想找到眼睛,但是在一個假陽性的世界裡,在面部裡面尋找眼睛,從邏輯上來說是不是很明智? 我們希望我們不尋找不在臉上的眼睛! 嚴格來說,「眼睛檢測」可能不會找到閒置的眼球。 大多數眼睛檢測使用周圍的皮膚,眼瞼,眼睫毛,眉毛也可以用於檢測。 因此,我們的下一步就是先去拆分面部,然後才能到達眼睛:
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
在這裡,我們找到了面部,它們的大小,繪製矩形,並注意 ROI。 接下來,我們找了一些眼睛:
eyes = eye_cascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
如果我們找到這些,我們會繼續繪製更多的矩形。 接下來我們完成:
cv2.imshow('img',img)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
完整代碼:
import numpy as np
import cv2
#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xml
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
cap = cv2.VideoCapture(0)
while 1:
ret, img = cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
cv2.imshow('img',img)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
不錯。你可能會注意到我不得不取下我的眼鏡。這些造成了一些麻煩。我的嘴也經常被檢測為眼睛,有時甚至是一張臉,但你明白了。面部毛髮和其他東西經常可以欺騙基本面部檢測,除此之外,皮膚的顏色也會造成很大的麻煩,因為我們經常試圖儘可能簡化圖像,從而失去了很多顏色值。甚至還有一個小型行業,可以避免人臉檢測和識別。CVDazzle 網站就是一個例子。其中有些非常古怪,但他們很有效。你也可以總是走完整的面部重建手術的路線,以避免自動跟蹤和檢測,所以總是這樣,但是這更永久。做個髮型比較短暫也容易做到。
好吧,檢測面部,眼睛和汽車是可以的,但我們是程式設計師。我們希望能夠做任何事情。事實證明,事情會變得相當混亂,建立自己的 Haar Cascades 有一定的難度,但是其他人也這麼做......你也可以!這就是在下一個教程中所討論的。
十七、創建自己的 Haar Cascade
歡迎使用 Python OpenCV 對象檢測教程。在本教程中,你將看到如何創建你自己的 Haar Cascades,以便你可以跟蹤任何你想要的對象。由於這個任務的本質和複雜性,本教程將比平時稍長一些,但獎勵是巨大的。
雖然你可以在 Windows 中完成,我不會建議這樣。因此,對於本教程,我將使用 Linux VPS,並且我建議你也這樣做。你可以嘗試使用 Amazon Web Services 提供的免費套餐,但對你來說可能太痛苦了,你可能需要更多的內存。你還可以從 Digital Ocean 獲得低至五美元/月的 VPS。我推薦至少 2GB 的內存用於我們將要做的事情。現在大多數主機按小時收費,包括 DO。因此,你可以購買一個 20 美元/月的伺服器,使用它一天,獲取你想要的文件,然後終止伺服器,並支付很少的錢。你需要更多的幫助來設置伺服器?如果是的話,看看這個具體的教程。
一旦你的伺服器準備就緒,你會打算獲取實際的 OpenCV 庫。
將目錄更改到伺服器的根目錄,或者你想放置工作區的地方:
cd ~
sudo apt-get update
sudo apt-get upgrade
首先,讓我們為自己製作一個漂亮的工作目錄:
mkdir opencv_workspace
cd opencv_workspace
既然我們完成了,讓我們獲取 OpenCV。
sudo apt-get install git
git clone https://github.com/Itseez/opencv.git
我們這裡克隆了 OpenCV 的最新版本。現在獲取一些必需品。
編譯器:sudo apt-get install build-essential
庫:sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
Python 綁定:sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
最後,讓我們獲取 OpenCV 開發庫:
sudo apt-get install libopencv-dev
現在,我們該如何完成這個過程呢?所以當你想建立一個 Haar Cascade 時,你需要「正片」圖像和「底片」圖像。 「正片」圖像是包含要查找的對象的圖像。這可以是具有對象的主要圖像,也可以是包含對象的圖像,並指定對象所在的 ROI(興趣區域)。有了這些正片圖像,我們建立一個矢量文件,基本上是所有這些東西放在一起。正片圖像的一個好處是,你可以實際只有一個你想要檢測的對象的圖像,然後有幾千個底片圖像。是的,幾千。底片圖像可以是任何東西,除了他們不能包含你的對象。
在這裡,使用你的底片圖像,你可以使用opencv_createsamples命令來創建一堆正片的示例。你的正片圖像將疊加在這些底片上,而且會形成各種各樣的角度。它實際上可以工作得很好,特別是如果你只是在尋找一個特定的對象。但是,如果你正在尋找所有螺絲刀,則需要擁有數千個螺絲刀的獨特圖像,而不是使用opencv_createsamples為你生成樣品。我們將保持簡單,只使用一個正片圖像,然後用我們的底片創建一堆樣本。
我們的正片圖像:
這是另外一個場景,如果你使用自己的圖像,你可能會更喜歡這個。如果事情出錯了,試試看我的,但是我建議你自己畫一下。保持較小。 50x50像素應該可以。
好吧,獲得正片圖像是沒有問題的!只有一個問題。我們需要成千上萬的底片圖像。可能在未來,我們也可能需要成千上萬的正片圖像。我們可以在世界的哪個地方實現它?基於 WordNet 的概念,有一個非常有用的站點叫做 ImageNet。從這裡,你可以找到幾乎任何東西的圖像。我們這裡,我們想要手錶,所以搜索手錶,你會發現大量種類的手錶。讓我們檢索電子表。真棒!看看下載標籤!存在用於所有電子表手錶的 URL!很酷。好吧,但我說過我們只會使用一個正片,所以我們只是檢測一個手錶。如果你想檢測「全部」手錶,需要準備獲取多餘 50,000 個手錶圖像,至少 25000 個「底片」的圖像。之後,準備足夠的伺服器,除非你想要你的 Haar Cascade 訓練花一個星期。那麼我們如何得到底片? ImageNet 的全部重點是圖像訓練,所以他們的圖像非常具體。因此,如果我們搜索人,汽車,船隻,飛機......無論什麼,都不會有手錶。你可能會看到一些人或類似的東西,但你明白了。既然你可能看到人周圍或上面的手錶,我其實認為你也會得到人的圖像。我的想法是尋找做運動的人,他們可能沒有戴電子表。所以,我們來找一些批量圖片的 URL 連結。我發現體育/田徑連結有 1,888 張圖片,但你會發現很多這些都是完全損壞的。讓我們再來找一個:
好吧,我們擁有所有這些圖片,現在呢?那麼,首先,我們希望所有這些大小都相同,而且要小很多!天哪,只要我們知道一個方法來操作圖像...嗯...哦,這是一個 OpenCV 教程!我們可以處理它。所以,首先,我們要做的就是編寫一個簡單的腳本,訪問這些 URL 列表,獲取連結,訪問連結,拉取圖像,調整大小,保存它們,然後重複,直到完成。當我們的目錄充滿圖像時,我們還需要一種描述圖像的描述文件。對於正片,手動創建這個文件特別痛苦,因為你需要指定你的對象,每個圖像的具體的興趣區域。幸運的是,create_samples方法將圖像隨機放置,並為我們做了所有工作。我們只需要一個用於底片的簡單描述符,但是這不是問題,在拉伸和操作圖像時我們可以實現。
在任何你喜歡的地方隨意運行這個代碼。 我要在我的主機上運行它,因為它應該快一點。 你可以在你的伺服器上運行。 如果你想使用cv2模塊,請執行sudo apt-get install python-OpenCV。 目前,我不知道在 Linux 上為 Python 3 獲得這些綁定的好方法。 我將要寫的腳本是 Python 3,所以記住這一點。 主要區別是Urllib處理。
import urllib.request
import cv2
import numpy as np
import os
def store_raw_images():
neg_images_link = '//image-net.org/api/text/imagenet.synset.geturls?wnid=n00523513'
neg_image_urls = urllib.request.urlopen(neg_images_link).read().decode()
pic_num = 1
if not os.path.exists('neg'):
os.makedirs('neg')
for i in neg_image_urls.split('\n'):
try:
print(i)
urllib.request.urlretrieve(i, "neg/"+str(pic_num)+".jpg")
img = cv2.imread("neg/"+str(pic_num)+".jpg",cv2.IMREAD_GRAYSCALE)
resized_image = cv2.resize(img, (100, 100))
cv2.imwrite("neg/"+str(pic_num)+".jpg",resized_image)
pic_num += 1
except Exception as e:
print(str(e))
很簡單,這個腳本將訪問連結,抓取網址,並繼續訪問它們。從這裡,我們抓取圖像,轉換成灰度,調整大小,然後保存。我們使用一個簡單的計數器來命名圖像。繼續運行它。你可能看到,有很多確實的圖片等。沒關係。這些錯誤圖片中的一些更有問題。基本上都是白色,帶有一些文本,說他們不再可用,而不是服務和 HTTP 錯誤。現在,我們有幾個選擇。我們可以忽略它們,或者修復它。嘿,這是一個沒有手錶的圖像,所以什麼是對的呢?當然,你可以採取這種觀點,但如果你為正片使用這種拉取方式的話,這肯定是一個問題。你可以手動刪除它們...或者我們可以使用我們新的圖像分析知識,來檢測這些愚蠢的圖像,並將其刪除!
我繼續生成了一個新的目錄,稱之為「uglies(醜陋)」。在那個目錄中,我點擊並拖動了所有醜陋的圖像版本(只是其中之一)。在底片中我只發現了一個主犯,所以我只有一個。讓我們編寫一個腳本來查找這個圖像的所有實例並刪除它。
def find_uglies():
match = False
for file_type in ['neg']:
for img in os.listdir(file_type):
for ugly in os.listdir('uglies'):
try:
current_image_path = str(file_type)+'/'+str(img)
ugly = cv2.imread('uglies/'+str(ugly))
question = cv2.imread(current_image_path)
if ugly.shape == question.shape and not(np.bitwise_xor(ugly,question).any()):
print('That is one ugly pic! Deleting!')
print(current_image_path)
os.remove(current_image_path)
except Exception as e:
print(str(e))
現在我們只有底片,但是我留下了空間讓你輕易在那裡添加'pos'(正片)。 你可以運行它來測試,但我不介意先抓住更多的底片。 讓我們再次運行圖片提取器,僅僅使用這個 url://image-net.org/api/text/imagenet.synset.geturls?wnid=n07942152。 最後一張圖像是#952,所以讓我們以 953 開始pic_num,並更改網址。
def store_raw_images():
neg_images_link = '//image-net.org/api/text/imagenet.synset.geturls?wnid=n07942152'
neg_image_urls = urllib.request.urlopen(neg_images_link).read().decode()
pic_num = 953
if not os.path.exists('neg'):
os.makedirs('neg')
for i in neg_image_urls.split('\n'):
try:
print(i)
urllib.request.urlretrieve(i, "neg/"+str(pic_num)+".jpg")
img = cv2.imread("neg/"+str(pic_num)+".jpg",cv2.IMREAD_GRAYSCALE)
resized_image = cv2.resize(img, (100, 100))
cv2.imwrite("neg/"+str(pic_num)+".jpg",resized_image)
pic_num += 1
except Exception as e:
print(str(e))
現在我們有超過2000張照片。 最後一步是,我們需要為這些底片圖像創建描述符文件。 我們將再次使用一些代碼!
def create_pos_n_neg():
for file_type in ['neg']:
for img in os.listdir(file_type):
if file_type == 'pos':
line = file_type+'/'+img+' 1 0 0 50 50\n'
with open('info.dat','a') as f:
f.write(line)
elif file_type == 'neg':
line = file_type+'/'+img+'\n'
with open('bg.txt','a') as f:
f.write(line)
運行它,你有了個bg.txt文件。 現在,我知道有些人的網際網路連接可能不是最好的,所以我做個好人,在這裡上傳底片圖片和描述文件。 你應該通過這些步驟。 如果你對本教程感到困擾,則需要知道如何執行這部分。 好吧,所以我們決定我們將一個圖像用於正片前景圖像。 因此,我們需要執行create_samples。 這意味著,我們需要將我們的neg目錄和bg.txt文件移動到我們的伺服器。 如果你在伺服器上運行所有這些代碼,不要擔心。
如果你是一個術士,並已經想出了如何在 Windows 上運行create_samples等,恭喜! 回到伺服器的領地,我的文件現在是這樣的:
opencv_workspace
--neg
----negimages.jpg
--opencv
--info
--bg.txt
--watch5050.jpg
你可能沒有info目錄,所以繼續並mkdir info。 這是我們放置所有正片圖像的地方。
我們現在準備根據watch5050.jpg圖像創建一些正片樣本。 為此,請在工作區中通過終端運行以下命令:
opencv_createsamples -img watch5050.jpg -bg bg.txt -info info/info.lst -pngoutput info -maxxangle 0.5 -maxyangle 0.5 -maxzangle 0.5 -num 1950
這樣做是基於我們指定的img創建樣本,bg是背景信息,我們將輸出info.list(很像bg.txt文件)的信息,然後-pngoutput就是我們想要放置新生成的圖像的任何地方。 最後,我們有一些可選的參數,使我們的原始圖像更加動態一些,然後用= num來表示我們想要創建的樣本數量。 太棒了,讓我們來運行它。 現在你的info目錄應該有約 2,000 個圖像,還有一個名為info.lst的文件。 這個文件基本上是你的「正片」文件。 打開它,並且看看它怎麼樣:
0001_0014_0045_0028_0028.jpg 1 14 45 28 28
首先你有文件名,之後是圖像中有多少對象,其次是它們的所有位置。 我們只有一個,所以它是圖像中對象矩形的x,y,寬度和高度。 這是一個圖像:
很難看到它,但如果你很難看到,手錶就是這個圖像。 圖像中最左側人物的左下方。 因此,這是一個「正片」圖像,從另外一個「底片」圖像創建,底片圖像也將用於訓練。 現在我們有了正片圖像,現在我們需要創建矢量文件,這基本上是一個地方,我們將所有正片圖像拼接起來。我們會再次為此使用opencv_createsamples!
opencv_createsamples -info info/info.lst -num 1950 -w 20 -h 20 -vec positives.vec
這是我們的矢量文件。 在這裡,我們只是讓它知道信息文件的位置,我們想要在文件中包含多少圖像,在這個矢量文件中圖像應該是什麼尺寸,然後才能輸出結果。 如果你願意的話,你可以做得更大一些,20×20可能足夠好了,你做的越大,訓練時間就會越長。 繼續,我們現在只需要訓練我們的層疊。
首先,我們要把輸出放在某個地方,所以讓我們創建一個新的數據目錄:
mkdir data,你的工作空間應該如下所示:
opencv_workspace
--neg
----negimages.jpg
--opencv
--info
--data
--positives.vec --bg.txt
--watch5050.jpg
現在讓我們運行訓練命令:
opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 1800 -numNeg 900 -numStages 10 -w 20 -h 20
在這裡,我們表明了,我們想要數據去的地方,矢量文件的位置,背景文件的位置,要使用多少個正片圖像和底片圖像,多少個階段以及寬度和高度。請注意,我們使用的numPos比我們少得多。這是為了給階段騰出空間。
有更多的選擇,但這些就夠了。這裡主要是正片和底片的數量。一般認為,對於大多數實踐,你需要 2:1 比例的正片和底片圖像。有些情況可能會有所不同,但這是人們似乎遵循的一般規則。接下來,我們擁有階段。我們選擇了 10 個。你至少要 10-20 個,越多需要的時間越長,而且是指數級的。第一階段通常很快,第五階段要慢得多,第五十個階段永遠不會做完!所以,我們現在執行 10 個階段。這裡不錯的事情是你可以訓練 10 個階段,稍後再回來,把數字改成 20,然後在你離開的地方繼續。同樣的,你也可以放一些像 100 個階段的東西,上床睡覺,早上醒來,停下來,看看你有多遠,然後用這些階段「訓練」,你會立即得到一個層疊文件。你可能從最後一句話中得出,這個命令的結果確實很棒,是個不錯的層疊文件。我們希望能檢測到我的手錶,或者你決定檢測的任何東西。我所知道的是,在輸出這一段的時候,我還沒有完成第一階段的工作。如果你真的想要在一夜之間運行命令,但不想讓終端打開,你可以使用nohup:
nohup opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 1800 -numNeg 900 -numStages 10 -w 20 -h 20 &
這使命令即使在關閉終端之後也能繼續運行。 你可以使用更多,但你可能會或可能不會用完你的 2GB RAM。
在我的 2GB DO 伺服器上,10 個階段花了不到 2 個小時的時間。 所以,要麼有一個cascade.xml文件,要麼停止腳本運行。 如果你停止運行,你應該在你的data目錄下有一堆stageX.xml文件。 打開它,看看你有多少個階段,然後你可以使用這些階段,再次運行opencv_traincascade,你會立即得到一個cascade.xml文件。 這裡,我只想說出它是什麼,以及有多少個階段。 對我來說,我做了 10 個階段,所以我將它重命名為watchcascade10stage.xml。 這就是我們所需的,所以現在將新的層次文件傳回主計算機,放在工作目錄中,讓我們試試看!
import numpy as np
import cv2
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
#this is the cascade we just made. Call what you want
watch_cascade = cv2.CascadeClassifier('watchcascade10stage.xml')
cap = cv2.VideoCapture(0)
while 1:
ret, img = cap.read()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
watches = watch_cascade.detectMultiScale(gray, 50, 50)
for (x,y,w,h) in watches:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,0),2)
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
eyes = eye_cascade.detectMultiScale(roi_gray)
for (ex,ey,ew,eh) in eyes:
cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)
cv2.imshow('img',img)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
你可能注意到,手錶的方框很小。 它似乎並沒有達到整個手錶。 回想一下我們的訓練規模是20x20。 因此,我們最多有個20x20的方框。 你可以做100x100,但是,要小心,這將需要很長時間來訓練。 因此,我們不繪製方框,而是,為什麼不在手錶上寫字或什麼東西? 這樣做相對簡單。 我們不在手錶上執行cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2),我們可以執行:
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'Watch',(x-w,y-h), font, 0.5, (11,255,255), 2, cv2.LINE_AA)
很酷! 所以你可能沒有使用我的手錶,你是怎麼做的? 如果遇到麻煩,請嘗試使用與我完全相同的所有內容。 檢測圖像,而不是檢測攝像頭,這裡是一個:
在圖像上運行檢測會給你:
我不了解你,但一旦我最終使其工作,我非常興奮!最讓我印象深刻的是,跟蹤對象所需的數據大小。Haar Cascades 往往是 100-2000 KB 的大小。大於等於 2,000 KB 的 Haar Cascades 應該非常準確。考慮你的情況,你可能會遇到約 5000 個一般物體。考慮 Haar Cascades 平均可能是約 500 KB。我們需要:0.5 MB * 5,000 = 2,500 MB或 2.5 GB。你需要 2.5 GB 的內存來識別 5000 個對象,並且可能是你在一天中遇到的最多對象。這讓我著迷。考慮到我們可以訪問所有的 image-net,並可以立即拉取很大範圍的對象的一般圖像。考慮 image-net 上的大多數圖像,基本上都是 100% 的「跟蹤」對象,因此,你可以通過手動標註位置,並僅使用 0,0 和圖像的全部大